﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NHibernate.Linq;
using Xenta.Api;
using Xenta.Entities;
using Xenta.Enums;
using Xenta.Plugins;
using Xenta.Security;
using Xenta.Utils;

namespace Xenta.Operations
{
    #region __General__

    /// <summary>
    /// Creates a order.
    /// </summary>
    public class CreateOrder : CreateAuditable<OrderEntity>,
        ISecuredOperation, IApiOperation
    {
        #region Properties

        /// <summary>
        /// Gets the collection of security policies.
        /// </summary>
        public IEnumerable<SecurityPolicy> SecurityPolicies
        {
            get
            {
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .HasPermission("SALES");
            }
        }

        /// <summary>
        /// Gets or sets the customer identifier.
        /// </summary>
        public int CustomerID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the seller identifier.
        /// </summary>
        public int SellerID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the billing address identifier.
        /// </summary>
        public int BillingAddressID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the shipping address identifier.
        /// </summary>
        public int ShippingAddressID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the shipping method global unique identifier.
        /// </summary>
        public Guid ShippingMethodID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the sales model global unique identifier.
        /// </summary>
        public Guid SalesModelID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the seller address identifier.
        /// </summary>
        public int SellerAddressID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the handling fee.
        /// </summary>
        public decimal HandlingFee
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the tax amount.
        /// </summary>
        public decimal TaxAmount
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the discount amount.
        /// </summary>
        public decimal DiscountAmount
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the shipping price.
        /// </summary>
        public decimal ShippingPrice
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the subtotal.
        /// </summary>
        public decimal Subtotal
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the total.
        /// </summary>
        public decimal Total
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the flags.
        /// </summary>
        public OrderFlags Flags
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override Err Validate(IUnitOfWork uow)
        {
            if(TaxAmount < Decimal.Zero)
                return Err.Of<OrderEntity>().NotValid("TaxAmount");
            if(DiscountAmount < Decimal.Zero)
                return Err.Of<OrderEntity>().NotValid("DiscountAmount");
            if(ShippingPrice < Decimal.Zero)
                return Err.Of<OrderEntity>().NotValid("ShippingPrice");
            if(HandlingFee < Decimal.Zero)
                return Err.Of<OrderEntity>().NotValid("HandlingFee");
            if(Subtotal < Decimal.Zero)
                return Err.Of<OrderEntity>().NotValid("Subtotal");
            if(Total < Decimal.Zero)
                return Err.Of<OrderEntity>().NotValid("Total");

            if(!uow.Infrastructure.CheckDependency<ISalesModel>(SalesModelID))
                return Err.Of<ISalesModel>().NotFound();
            if(!uow.Infrastructure.CheckDependency<IShippingMethod>(ShippingMethodID))
                return Err.Of<IShippingMethod>().NotFound();

            var customer = uow.Execute(new GetCustomer
            {
                ThrowsException = true,
                EntityID = CustomerID
            }).Result;
            var billingAddress = uow.Execute(new GetCustomerAddress
            {
                ThrowsException = true,
                EntityID = BillingAddressID
            }).Result;
            if(billingAddress.HolderID != customer.EntityID)
                return Err.Of(this).NotValid("BillingAddress");
            var shippingAddress = uow.Execute(new GetCustomerAddress
            {
                ThrowsException = true,
                EntityID = ShippingAddressID
            }).Result;
            if(shippingAddress.HolderID != customer.EntityID)
                return Err.Of(this).NotValid("ShippingAddress");

            var seller = uow.Execute(new GetSeller
            {
                ThrowsException = true,
                EntityID = SellerID
            }).Result;
            var sellerAddress = uow.Execute(new GetSellerAddress
            {
                ThrowsException = true,
                EntityID = SellerAddressID
            }).Result;
            if(sellerAddress.HolderID != seller.EntityID)
                return Err.Of(this).NotValid("SellerAddress");

            using(var ioc = uow.Infrastructure.DependencyResolver())
            {
                var shippingMethod = ioc.Resolve(ShippingMethodID)
                    as IShippingMethod;
                if(!shippingMethod.CanShipFrom(sellerAddress) ||
                    !shippingMethod.CanShipTo(shippingAddress))
                {
                    return Err.Of(this).NotValid("ShippingMethod");
                }
            }

            return base.Validate(uow);
        }

        protected override void Construct(IUnitOfWork uow, OrderEntity entity)
        {
            base.Construct(uow, entity);

            entity.Guid = Guid.NewGuid();
            entity.Customer = uow.Execute(new GetCustomer
            {
                ThrowsException = true,
                EntityID = CustomerID
            }).Result;
            entity.Seller = uow.Execute(new GetSeller
            {
                ThrowsException = true,
                EntityID = SellerID
            }).Result;
            entity.BillingAddress = uow.Execute(new GetCustomerAddress
            {
                ThrowsException = true,
                EntityID = BillingAddressID
            }).Result;
            entity.ShippingAddress = uow.Execute(new GetCustomerAddress
            {
                ThrowsException = true,
                EntityID = ShippingAddressID
            }).Result;
            entity.ShippingMethodID = ShippingMethodID;
            entity.SalesModelID = SalesModelID;
            entity.SellerAddress = uow.Execute(new GetSellerAddress
            {
                ThrowsException = true,
                EntityID = SellerAddressID
            }).Result;
            entity.HandlingFee = HandlingFee;
            entity.TaxAmount = TaxAmount;
            entity.DiscountAmount = DiscountAmount;
            entity.ShippingPrice = ShippingPrice;
            entity.Subtotal = Subtotal;
            entity.Total = Total;
            entity.Flags = Flags;
        }

        #endregion
    }

    /// <summary>
    /// Updates the order.
    /// </summary>
    public class UpdateOrder : UpdateAuditable<OrderEntity>,
        ISecuredOperation, IApiOperation
    {
        #region Properties

        /// <summary>
        /// Gets the collection of security policies.
        /// </summary>
        public IEnumerable<SecurityPolicy> SecurityPolicies
        {
            get
            {
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .HasPermission("SALES");
            }
        }

        /// <summary>
        /// Gets or sets the handling fee.
        /// </summary>
        public decimal HandlingFee
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the tax amount.
        /// </summary>
        public decimal TaxAmount
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the discount amount.
        /// </summary>
        public decimal DiscountAmount
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the shipping price.
        /// </summary>
        public decimal ShippingPrice
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the subtotal.
        /// </summary>
        public decimal Subtotal
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the total.
        /// </summary>
        public decimal Total
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the flags.
        /// </summary>
        public OrderFlags Flags
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override Err Validate(IUnitOfWork uow)
        {
            if(TaxAmount < Decimal.Zero)
                return Err.Of<OrderEntity>().NotValid("TaxAmount");
            if(DiscountAmount < Decimal.Zero)
                return Err.Of<OrderEntity>().NotValid("DiscountAmount");
            if(ShippingPrice < Decimal.Zero)
                return Err.Of<OrderEntity>().NotValid("ShippingPrice");
            if(HandlingFee < Decimal.Zero)
                return Err.Of<OrderEntity>().NotValid("HandlingFee");
            if(Subtotal < Decimal.Zero)
                return Err.Of<OrderEntity>().NotValid("Subtotal");
            if(Total < Decimal.Zero)
                return Err.Of<OrderEntity>().NotValid("Total");
            return base.Validate(uow);
        }

        protected override void Revise(IUnitOfWork uow, OrderEntity entity)
        {
            base.Revise(uow, entity);

            entity.HandlingFee = HandlingFee;
            entity.TaxAmount = TaxAmount;
            entity.DiscountAmount = DiscountAmount;
            entity.ShippingPrice = ShippingPrice;
            entity.Subtotal = Subtotal;
            entity.Total = Total;
            entity.Flags = Flags;
        }

        #endregion
    }

    /// <summary>
    /// Deletes the order.
    /// </summary>
    public class DeleteOrder : DeleteEntity<OrderEntity>,
        ISecuredOperation, IApiOperation
    {
        #region Properties

        /// <summary>
        /// Gets the collection of security policies.
        /// </summary>
        public IEnumerable<SecurityPolicy> SecurityPolicies
        {
            get
            {
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .HasPermission("SALES");
            }
        }

        #endregion

        #region Utilities

        protected override void Destruct(IUnitOfWork uow, OrderEntity entity)
        {
            foreach(var i in entity.Attributes)
            {
                uow.Execute(new DeleteOrderAttribute
                {
                    EntityID = i.EntityID
                });
            }
            foreach(var i in entity.Comments)
            {
                uow.Execute(new DeleteOrderComment
                {
                    EntityID = i.EntityID
                });
            }
            foreach(var i in entity.Items)
            {
                uow.Execute(new DeleteOrderItem
                {
                    EntityID = i.EntityID
                });
            }
            base.Destruct(uow, entity);
        }

        #endregion
    }

    /// <summary>
    /// Gets the order by identifier.
    /// </summary>
    public class GetOrder : QueryEntityByID<OrderEntity>,
        IApiOperation
    {
        #region Utilities

        protected override IQueryable<OrderEntity> Build(IUnitOfWork uow, IQueryable<OrderEntity> query)
        {
            query = base.Build(uow, query);
            if(Fetch > FetchLevel.None)
            {
                query = query.Fetch(x => x.Customer)
                    .ThenFetch(x => x.Account)
                    .Fetch(x => x.Seller)
                    .ThenFetch(x => x.Account)
                    .Fetch(x => x.BillingAddress)
                    .Fetch(x => x.SellerAddress);
            }
            return query;
        }

        #endregion
    }

    /// <summary>
    /// Gets the order by guid.
    /// </summary>
    public class GetOrderByGuid : QueryEntity<OrderEntity>,
        IApiOperation
    {
        #region Properties

        /// <summary>
        /// Gets or sets the account guid.
        /// </summary>
        public Guid Guid
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override IQueryable<OrderEntity> Build(IUnitOfWork uow, IQueryable<OrderEntity> query)
        {
            query = base.Build(uow, query);
            query = query.Where(x => x.Guid == Guid);
            if(Fetch > FetchLevel.None)
            {
                query = query.Fetch(x => x.Customer)
                    .ThenFetch(x => x.Account)
                    .Fetch(x => x.Seller)
                    .ThenFetch(x => x.Account)
                    .Fetch(x => x.BillingAddress)
                    .Fetch(x => x.SellerAddress);
            }
            return query;
        }

        #endregion
    }

    #endregion

    #region __Search__

    /// <summary>
    /// Search over order collection.
    /// </summary>
    public class SearchOrders : SearchOperation<OrderEntity>,
        IApiOperation
    {
        #region Ctors

        /// <summary>
        /// Initializes a new class instance.
        /// </summary>
        public SearchOrders()
        {
            SortDir = SortDirection.Ascending;
            SortBy = new []
            {
                OrderSortBy.ID
            };
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the sales model global 
        /// unique identifier.
        /// </summary>
        public Guid? SalesModelID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the sales model global 
        /// unique identifier.
        /// </summary>
        public Guid? ShippingMethodID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the customer identifier.
        /// </summary>
        public int? CustomerID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the seller identifier.
        /// </summary>
        public int? SellerID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the flag mask.
        /// </summary>
        public OrderFlags FlagMask
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the creation date 'from'.
        /// </summary>
        public DateTime? CreatedFrom
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the creation date 'to'.
        /// </summary>
        public DateTime? CreatedTo
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the sort direction.
        /// </summary>
        public SortDirection SortDir
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the sort by.
        /// </summary>
        public OrderSortBy[] SortBy
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override IQueryable<OrderEntity> Build(IUnitOfWork uow, IQueryable<OrderEntity> query)
        {
            query = base.Build(uow, query);
            query = query.Fetch(x => x.Customer)
                .ThenFetch(x => x.Account)
                .Fetch(x => x.BillingAddress);
            query = query.FilterByFlags(x => (int)x.Flags, (int)FlagMask);
            query = query.FilterByDateRange(x => x.CreatedOn, CreatedFrom, CreatedTo);
            if(SalesModelID.HasValue)
                query = query.Where(x => x.SalesModelID == SalesModelID.Value);
            if(ShippingMethodID.HasValue)
                query = query.Where(x => x.ShippingMethodID == ShippingMethodID.Value);
            if(CustomerID.HasValue)
                query = query.Where(x => x.Customer.EntityID == CustomerID.Value);
            if(SellerID.HasValue)
                query = query.Where(x => x.Seller.EntityID == SellerID.Value);
            if(SortBy != null)
            {
                for(int i = 0; i < SortBy.Length; i++)
                {
                    switch(SortBy[i])
                    {
                        case OrderSortBy.Total:
                            query = query.OrderBy(x => x.Total, SortDir, i != 0);
                            break;
                        case OrderSortBy.CreatedOn:
                            query = query.OrderBy(x => x.CreatedOn, SortDir, i != 0);
                            break;
                        default:
                            query = query.OrderBy(x => x.EntityID, SortDir, i != 0);
                            break;
                    }
                }
            }
            return query;
        }

        #endregion
    }

    #endregion

    #region __Workflow__

    /// <summary>
    /// Canceles the order.
    /// </summary>
    public class CancelOrder : OperateEntityByID<OrderEntity>,
        ISecuredOperation, IApiOperation
    {
        #region Properties

        /// <summary>
        /// Gets the collection of security policies.
        /// </summary>
        public IEnumerable<SecurityPolicy> SecurityPolicies
        {
            get
            {
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .HasPermission("SALES");
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .IsVerified()
                    .Rule((ctx, uow) =>
                    {
                        var customer = ctx.Customer(uow);
                        if(customer == null || !customer.Flags.HasFlag(CustomerFlags.Active))
                            return false;
                        var order = uow.Execute(new GetOrder
                        {
                            EntityID = EntityID
                        }).Result;
                        if(order == null)
                            return false;
                        return order.Customer.Equals(customer);
                    });
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .IsVerified()
                    .Rule((ctx, uow) =>
                    {
                        var seller = ctx.Seller(uow);
                        if(seller == null || !seller.Flags.HasFlag(SellerFlags.Active))
                            return false;
                        var order = uow.Execute(new GetOrder
                        {
                            EntityID = EntityID
                        }).Result;
                        if(order == null)
                            return false;
                        return order.Seller.Equals(seller);
                    });
            }
        }

        #endregion

        #region Utilities

        protected override void Work(IUnitOfWork uow, out OrderEntity result)
        {
            var order = uow.Execute(new GetOrder
            {
                ThrowsException = true,
                EntityID = EntityID
            }).Result;
            if(order.Flags.HasFlag(OrderFlags.Processing))
            {
                foreach(var i in order.Items)
                {
                    uow.Execute(new AdjustStockReserve
                    {
                        SKU = i.SKU,
                        Quantity = Decimal.Negate(i.Quantity)
                    });
                }
                foreach(var i in order.AppliedRules)
                {
                    uow.Execute(new RollbackSalesRule
                    {
                        HistoryID = i.EntityID
                    });
                }
            }
            result = uow.Execute(new SetOrderStatus
            {
                EntityID = EntityID,
                Status = OrderFlags.Canceled
            }).Result;
        }

        #endregion
    }

    /// <summary>
    /// Places an order.
    /// </summary>
    public class PlaceOrder : Operation<OrderEntity>,
        ISecuredOperation, IApiOperation
    {
        #region Properties

        /// <summary>
        /// Gets the collection of security policies.
        /// </summary>
        public IEnumerable<SecurityPolicy> SecurityPolicies
        {
            get
            {
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .HasPermission("SALES");
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .IsVerified()
                    .Rule((ctx, uow) =>
                    {
                        var customer = ctx.Customer(uow);
                        if(customer == null || !customer.Flags.HasFlag(CustomerFlags.Active))
                            return false;
                        var cart = uow.Execute(new GetShoppingCart
                        {
                            EntityID = CartID
                        }).Result;
                        if(cart == null)
                            return false;
                        return cart.Customer.Equals(customer);
                    });
            }
        }

        /// <summary>
        /// Gets or sets the seller identifier.
        /// </summary>
        public int SellerID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the shopping cart identifier.
        /// </summary>
        public int CartID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the billing address identifier.
        /// </summary>
        public int BillingAddressID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the shipping address identifier.
        /// </summary>
        public int ShippingAddressID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the shipping method global unique identifier.
        /// </summary>
        public Guid ShippingMethodID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the sales model global unique identifier.
        /// </summary>
        public Guid SalesModelID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the seller address identifier.
        /// </summary>
        public int SellerAddressID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the attributes.
        /// </summary>
        public IDictionary<String, String> Attributes
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override Err Validate(IUnitOfWork uow)
        {
            var cart = uow.Execute(new GetShoppingCart
            {
                ThrowsException = true,
                EntityID = CartID
            }).Result;
            var cartItems = cart.Items.Where(x => x.SalesItem.Seller.EntityID == SellerID);
            if(!cartItems.Any())
                return Err.Of(this).No("ItemsInCart");
            if(cartItems.Any(x => !uow.Execute(new CheckStockReserve
            {
                SKU = x.SalesItem.SKU,
                Quantity = x.Quantity
            }).Result))
            {
                return Err.Of<SalesItemEntity>().OutOf("Stock");
            }
            if(!uow.Infrastructure.CheckDependency<ISalesModel>(SalesModelID))
                return Err.Of<ISalesModel>().NotFound();
            using(var ioc = uow.Infrastructure.DependencyResolver())
            {
                var model = ioc.Resolve(SalesModelID) as ISalesModel;
                if(cartItems.Any(x => !model.CanSale(x.SalesItem)))
                    return Err.Of(model).NotAllowed();
            }
            return base.Validate(uow);
        }

        protected override void Work(IUnitOfWork uow, out OrderEntity result)
        {
            var cart = uow.Execute(new GetShoppingCart
            {
                ThrowsException = true,
                EntityID = CartID
            }).Result;
            var cartItems = cart.Items.Where(x => x.SalesItem.Seller.EntityID == SellerID);
            var customer = cart.Customer;
            var order = uow.Execute(new CreateOrder
            {
                SellerID = SellerID,
                BillingAddressID = BillingAddressID,
                ShippingAddressID = ShippingAddressID,
                ShippingMethodID = ShippingMethodID,
                SalesModelID = SalesModelID,
                CustomerID = customer.EntityID,
                SellerAddressID = SellerAddressID,
                TaxAmount = Decimal.Zero,
                HandlingFee = Decimal.Zero,
                DiscountAmount = Decimal.Zero,
                ShippingPrice = Decimal.Zero,
                Subtotal = Decimal.Zero,
                Total = Decimal.Zero,
                Flags = OrderFlags.None
            }).Result;
            var dtNow = DateTime.UtcNow;
            decimal subtotal = Decimal.Zero;
            decimal total = Decimal.Zero;
            decimal tax = Decimal.Zero;
            decimal discount = Decimal.Zero;
            decimal shipping = Decimal.Zero;
            decimal fee = Decimal.Zero;
            var attributes = Attributes != null
                ? new Dictionary<String, String>(Attributes)
                : new Dictionary<String, String>();
            var comment = new StringBuilder();

            foreach(var opGroup in cart.Options.GroupBy(x => x.CodeName))
            {
                string opName = opGroup.Key;
                string opValue = opGroup.Select(x => x.CodeValue).Merge(",");
                var opSum = opGroup.Sum(x => x.Price);
                attributes.Add(opName, opValue);
                subtotal += opSum;
                total += opSum;
                comment.AppendFormat("+ {0}: {1}\n", opName, opValue);
            }
            if(attributes.Any())
            {
                uow.Execute(new BulkUpdateOrderAttributes
                {
                    HolderID = order.EntityID,
                    Attributes = attributes
                });
                uow.Flush();
                order = uow.Execute(new RefreshEntity<OrderEntity>
                {
                    Entity = order
                }).Result;
            }

            foreach(var i in cartItems)
            {
                decimal iPrice = i.Price;
                var iAttributes = new Dictionary<String, String>();

                foreach(var j in i.Options.GroupBy(x => x.CodeName))
                {
                    iAttributes.Add(j.Key, j.Select(x => x.CodeValue).Merge(","));
                    iPrice += j.Sum(x => x.Price);
                }
                foreach(var j in i.SalesItem.CatalogItem.Specifications.Where(x => x.SKU.Equals(i.SalesItem.SKU)))
                    foreach(var kv in j.ToDictionary())
                        iAttributes.Add(String.Format("{0}[{1}]", j.Code, kv.Key), kv.Value);

                decimal iSubtotal = iPrice * i.Quantity;
                var item = uow.Execute(new CreateOrderItem
                {
                    OrderID = order.EntityID,
                    Price = iPrice,
                    SKU = i.SalesItem.SKU,
                    Quantity = i.Quantity,
                    DiscountAmount = Decimal.Zero,
                    TaxAmount = Decimal.Zero,
                    Subtotal = iSubtotal,
                    Total = iSubtotal,
                    Flags = OrderItemFlags.None
                }).Result;

                if(iAttributes.Any())
                {
                    uow.Execute(new BulkUpdateOrderItemAttributes
                    {
                        HolderID = item.EntityID,
                        Attributes = iAttributes
                    });
                    uow.Flush();
                    item = uow.Execute(new RefreshEntity<OrderItemEntity>
                    {
                        Entity = item
                    }).Result;
                }
                var iRules = i.SalesItem.Rules.Where(x => 
                    x.CommonFlags.HasFlag(SalesRuleFlags.Active) &&
                    x.ValidFrom <= dtNow && x.ValidTo >= dtNow);
                foreach(var j in iRules.OrderBy(x => x.Priority))
                {
                    if(j.Roles.Any() && !customer.Account.Roles.Intersect(j.Roles).Any())
                        continue;
                    item = uow.Execute(new ApplySalesRuleToOrderItem
                    {
                        ItemID = item.EntityID,
                        RuleID = j.EntityID
                    }).Result;
                }

                comment.AppendFormat("+ {0} of {1}\n", item.Quantity, item.SKU);
                foreach(var j in i.Options.GroupBy(x => x.CodeName))
                    comment.AppendFormat("| - {0}: {1}\n", j.Key, j.Select(x => x.CodeValue).Merge(","));

                tax += item.TaxAmount;
                discount += item.DiscountAmount;
                subtotal += item.Subtotal;
                total += item.Total;
            }
            using(var ioc = uow.Infrastructure.DependencyResolver())
            {
                shipping = (ioc.Resolve(ShippingMethodID) as IShippingMethod)
                    .CalcShippingPrice(order);
            }
            subtotal += shipping;
            total += shipping;
            order = uow.Execute(new UpdateOrder
            {
                EntityID = order.EntityID,
                HandlingFee = order.HandlingFee,
                ShippingPrice = shipping,
                TaxAmount = tax,
                DiscountAmount = discount,
                Subtotal = subtotal,
                Total = total,
                Flags = order.Flags
            }).Result;

            using(var ioc = uow.Infrastructure.DependencyResolver())
            {
                fee = (ioc.Resolve(SalesModelID) as ISalesModel)
                    .CalcHandlingFee(order);
            }
            total += fee;
            order = uow.Execute(new UpdateOrder
            {
                EntityID = order.EntityID,
                TaxAmount = order.TaxAmount,
                HandlingFee = fee,
                DiscountAmount = order.DiscountAmount,
                ShippingPrice = order.ShippingPrice,
                Subtotal = order.Subtotal,
                Total = total,
                Flags = order.Flags
            }).Result;

            var oRules = uow.Execute(new SearchSalesRules
            {
                ValidOn = dtNow,
                FlagMask = SalesRuleFlags.Active | SalesRuleFlags.OrderScope
            }).Result;
            foreach(var i in oRules.OrderBy(x => x.Priority))
            {
                if(i.Roles.Any() && !customer.Account.Roles.Intersect(i.Roles).Any())
                    continue;
                order = uow.Execute(new ApplySalesRuleToOrder
                {
                    OrderID = order.EntityID,
                    RuleID = i.EntityID
                }).Result;
            }
            if(comment.Length != 0)
            {
                uow.Execute(new CreateOrderComment
                {
                    HolderID = order.EntityID,
                    Flags = OrderCommentFlags.Active | OrderCommentFlags.Public,
                    Text = comment.ToString(),
                    SuppressNotification = true
                });
            }

            result = uow.Execute(new IssueOrder
            {
                EntityID = order.EntityID
            }).Result;
        }

        #endregion
    }

    /// <summary>
    /// Issues the order.
    /// </summary>
    public class IssueOrder : OperateEntityByID<OrderEntity>,
        ISecuredOperation, IApiOperation
    {
        #region Properties

        /// <summary>
        /// Gets the collection of security policies.
        /// </summary>
        public IEnumerable<SecurityPolicy> SecurityPolicies
        {
            get
            {
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .HasPermission("SALES");
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .IsVerified()
                    .Rule((ctx, uow) =>
                    {
                        var customer = ctx.Customer(uow);
                        if(customer == null || !customer.Flags.HasFlag(CustomerFlags.Active))
                            return false;
                        var order = uow.Execute(new GetOrder
                        {
                            EntityID = EntityID
                        }).Result;
                        if(order == null)
                            return false;
                        return order.Customer.Equals(customer);
                    });
            }
        }

        #endregion

        #region Utilities

        protected override void Work(IUnitOfWork uow, out OrderEntity result)
        {
            result = uow.Execute(new SetOrderStatus
            {
                EntityID = EntityID,
                Status = OrderFlags.Pending
            }).Result;
        }

        #endregion
    }

    /// <summary>
    /// Confirms the order.
    /// </summary>
    public class ConfirmOrder : OperateEntityByID<OrderEntity>,
        ISecuredOperation, IApiOperation
    {
        #region Properties

        /// <summary>
        /// Gets the collection of security policies.
        /// </summary>
        public IEnumerable<SecurityPolicy> SecurityPolicies
        {
            get
            {
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .HasPermission("SALES");
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .IsVerified()
                    .Rule((ctx, uow) =>
                    {
                        var customer = ctx.Customer(uow);
                        if(customer == null || !customer.Flags.HasFlag(CustomerFlags.Active))
                            return false;
                        var order = uow.Execute(new GetOrder
                        {
                            EntityID = EntityID
                        }).Result;
                        if(order == null)
                            return false;
                        return order.Customer.Equals(customer);
                    });
            }
        }

        #endregion

        #region Utilities

        protected override void Work(IUnitOfWork uow, out OrderEntity result)
        {
            result = uow.Execute(new SetOrderStatus
            {
                EntityID = EntityID,
                Status = OrderFlags.Confirmed
            }).Result;
        }

        #endregion
    }

    /// <summary>
    /// Processes the order.
    /// </summary>
    public class ProcessOrder : OperateEntityByID<OrderEntity>,
        ISecuredOperation, IApiOperation
    {
        #region Properties

        /// <summary>
        /// Gets the collection of security policies.
        /// </summary>
        public IEnumerable<SecurityPolicy> SecurityPolicies
        {
            get
            {
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .HasPermission("SALES");
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .IsVerified()
                    .Rule((ctx, uow) =>
                    {
                        var seller = ctx.Seller(uow);
                        if(seller == null || !seller.Flags.HasFlag(SellerFlags.Active))
                            return false;
                        var order = uow.Execute(new GetOrder
                        {
                            EntityID = EntityID
                        }).Result;
                        if(order == null)
                            return false;
                        return order.Seller.Equals(seller);
                    });
            }
        }

        #endregion

        #region Utilities

        protected override void Work(IUnitOfWork uow, out OrderEntity result)
        {
            var order = uow.Execute(new GetOrder
            {
                ThrowsException = true,
                EntityID = EntityID
            }).Result;
            foreach(var i in order.AppliedRules)
            {
                uow.Execute(new CommitSalesRule
                {
                    HistoryID = i.EntityID
                });
            }
            foreach(var i in order.Items)
            {
                uow.Execute(new AdjustStockReserve
                {
                    SKU = i.SKU,
                    Quantity = i.Quantity
                });
            }
            result = uow.Execute(new SetOrderStatus
            {
                EntityID = EntityID,
                Status = OrderFlags.Processing
            }).Result;
        }

        #endregion
    }

    /// <summary>
    /// Completes the order.
    /// </summary>
    public class CompleteOrder : OperateEntityByID<OrderEntity>,
        ISecuredOperation, IApiOperation
    {
        #region Properties

        /// <summary>
        /// Gets the collection of security policies.
        /// </summary>
        public IEnumerable<SecurityPolicy> SecurityPolicies
        {
            get
            {
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .HasPermission("SALES");
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .IsVerified()
                    .Rule((ctx, uow) =>
                    {
                        var seller = ctx.Seller(uow);
                        if(seller == null || !seller.Flags.HasFlag(SellerFlags.Active))
                            return false;
                        var order = uow.Execute(new GetOrder
                        {
                            EntityID = EntityID
                        }).Result;
                        if(order == null)
                            return false;
                        return order.Seller.Equals(seller);
                    });
            }
        }

        #endregion

        #region Utilities

        protected override void Work(IUnitOfWork uow, out OrderEntity result)
        {
            result = uow.Execute(new SetOrderStatus
            {
                EntityID = EntityID,
                Status = OrderFlags.Completed
            }).Result;
        }

        #endregion
    }

    /// <summary>
    /// Sets the order status.
    /// </summary>
    public class SetOrderStatus : UpdateAuditable<OrderEntity>
    {
        #region Properties

        /// <summary>
        /// Gets or sets the order status.
        /// </summary>
        public OrderFlags Status
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override Err Validate(IUnitOfWork uow)
        {
            var entity = uow.Execute(new GetOrder
            {
                ThrowsException = true,
                EntityID = EntityID
            }).Result;
            var oldStatus = FlagsHelper.G1_Flags(entity.Flags);
            var newStatus = FlagsHelper.G1_Flags(Status);

            if(newStatus == oldStatus)
                return Err.Of(this).NotAllowed();
            switch(newStatus)
            {
                case OrderFlags.Canceled:
                    if(oldStatus == OrderFlags.Completed)
                        return Err.Of(this).NotAllowed();
                    break;
                case OrderFlags.Pending:
                    if(oldStatus != OrderFlags.None && oldStatus != OrderFlags.Canceled)
                        return Err.Of(this).NotAllowed();
                    break;
                case OrderFlags.Confirmed:
                    if(oldStatus != OrderFlags.Pending)
                        return Err.Of(this).NotAllowed();
                    break;
                case OrderFlags.Processing:
                    if(oldStatus != OrderFlags.Confirmed)
                        return Err.Of(this).NotAllowed();
                    break;
                case OrderFlags.Completed:
                    if(oldStatus != OrderFlags.Processing)
                        return Err.Of(this).NotAllowed();
                    break;
                default:
                    return Err.Of(this).NotValid("Status");
            }
            return base.Validate(uow);
        }

        protected override void Work(IUnitOfWork uow, out OrderEntity result)
        {
            base.Work(uow, out result);

            using(var ioc = uow.Infrastructure.DependencyResolver())
            {
                (ioc.Resolve(result.SalesModelID) as ISalesModel)
                    .OnOrderStatusChanged(uow, result);
            }
            try
            {
                switch(FlagsHelper.G1_Flags(result.Flags))
                {
                    case OrderFlags.Confirmed:
                        uow.Execute(new EnqueueOrderConfirmNofitication
                        {
                            OrderID = result.EntityID,
                            RecipientID = result.Customer.Account.EntityID
                        });
                        uow.Execute(new EnqueueOrderConfirmNofitication
                        {
                            OrderID = result.EntityID,
                            RecipientID = result.Seller.Account.EntityID
                        });
                        break;
                    case OrderFlags.Processing:
                        uow.Execute(new EnqueueOrderProcessNofitication
                        {
                            OrderID = result.EntityID,
                            RecipientID = result.Customer.Account.EntityID
                        });
                        uow.Execute(new EnqueueOrderProcessNofitication
                        {
                            OrderID = result.EntityID,
                            RecipientID = result.Seller.Account.EntityID
                        });
                        break;
                    case OrderFlags.Canceled:
                        uow.Execute(new EnqueueOrderCancelNofitication
                        {
                            OrderID = result.EntityID,
                            RecipientID = result.Customer.Account.EntityID
                        });
                        uow.Execute(new EnqueueOrderCancelNofitication
                        {
                            OrderID = result.EntityID,
                            RecipientID = result.Seller.Account.EntityID
                        });
                        break;
                    case OrderFlags.Completed:
                        uow.Execute(new EnqueueOrderCompleteNofitication
                        {
                            OrderID = result.EntityID,
                            RecipientID = result.Customer.Account.EntityID
                        });
                        uow.Execute(new EnqueueOrderCompleteNofitication
                        {
                            OrderID = result.EntityID,
                            RecipientID = result.Seller.Account.EntityID
                        });
                        break;
                }
            }
            catch(Exception ex)
            {
                uow.Execute(new CreateLogRecordFromException
                {
                    Ex = ex
                });
            }
        }

        protected override void Revise(IUnitOfWork uow, OrderEntity entity)
        {
            base.Revise(uow, entity);

            entity.Flags &= (~FlagsHelper.G1_Flags(entity.Flags));
            entity.Flags |= FlagsHelper.G1_Flags(Status);
            if(entity.Flags.HasFlag(OrderFlags.Confirmed) ||
                entity.Flags.HasFlag(OrderFlags.Processing))
            {
                entity.Flags |= OrderFlags.Active;
            }
            else
                entity.Flags &= (~OrderFlags.Active);
        }

        #endregion
    }

    #endregion

    #region __Notification__

    /// <summary>
    /// Enqueues an order confirmation notification message.
    /// </summary>
    /// <remarks>
    /// Message template code: NORDCFRM
    /// Allowed tokens: [T:Recipient.EntityID], [T:Recipient.Guid], [T:Recipient.Email], [T:Recipient.Username],
    /// [T:Order.Customer.EntityID], [T:Order.Customer.Account.Email], [T:Order.Customer.Account.Username],
    /// [T:Order.Customer.Account.Guid], [T:Order.Seller.EntityID], [T:Order.Seller.Account.Email],
    /// [T:Order.Seller.Account.Username], [T:Order.Seller.Account.Guid], [T:Order.ShippingMethodID],
    /// [T:Order.BillingAddress.Country], [T:Order.BillingAddress.Region], [T:Order.BillingAddress.City],
    /// [T:Order.BillingAddress.Address1], [T:Order.BillingAddress.Address2], [T:Order.BillingAddress.ContactName],
    /// [T:Order.BillingAddress.PostalCode], [T:Order.BillingAddress.PhoneNumber], [T:Order.BillingAddress.FaxNumber],
    /// [T:Order.BillingAddress.Email], [T:Order.ShippingAddress.Country], [T:Order.ShippingAddress.Region],
    /// [T:Order.ShippingAddress.City], [T:Order.ShippingAddress.Address1], [T:Order.ShippingAddress.Address2],
    /// [T:Order.ShippingAddress.ContactName], [T:Order.ShippingAddress.PostalCode], [T:Order.ShippingAddress.PhoneNumber],
    /// [T:Order.ShippingAddress.FaxNumber], [T:Order.ShippingAddress.Email], [T:Order.SellerAddress.Country],
    /// [T:Order.SellerAddress.Region], [T:Order.SellerAddress.City], [T:Order.SellerAddress.Address1],
    /// [T:Order.SellerAddress.Address2], [T:Order.SellerAddress.ContactName], [T:Order.SellerAddress.PostalCode],
    /// [T:Order.SellerAddress.PhoneNumber], [T:Order.SellerAddress.FaxNumber], [T:Order.SellerAddress.Email],
    /// [T:Order.Guid], [T:Order.DiscountAmount], [T:Order.TaxAmount], [T:Order.Subtotal], [T:Order.Total],
    /// [T:Order.CreatedOn], [T:Seller.DisplayName], [T:Order.Customer.Account.EntityID],
    /// [T:Order.Seller.Account.EntityID], [T:Order.SellerAddress.EntityName], [T:Order.Status], 
    /// [T:Order.SellerAddress.Website], [T:Order.EntityID]
    /// </remarks>
    public class EnqueueOrderConfirmNofitication : Operation<MessageEntity>
    {
        #region Properties

        /// <summary>
        /// Gets or sets the order identifier.
        /// </summary>
        public int OrderID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the recipient identifier.
        /// </summary>
        public int RecipientID
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override void Work(IUnitOfWork uow, out MessageEntity result)
        {
            var order = uow.Execute(new GetOrder
            {
                ThrowsException = true,
                EntityID = OrderID
            }).Result;
            var recipient = uow.Execute(new GetAccount
            {
                ThrowsException = true,
                EntityID = RecipientID
            }).Result;
            var sysLocale = uow.Execute(new GetLocalizationProfileByCode
            {
                ThrowsException = true,
                Code = uow.Execute(new GetConfigParamValueByKey
                {
                    Key = Par.In("Core").Of<LocalizationProfileEntity>().Name("System")
                }).Result
            }).Result;
            var usrLocale = recipient.LocalizationProfile;
            var numFmt = usrLocale.Country.NumericFormat;
            int utcOffset = usrLocale.TimeZone.UtcOffset;
            string dtFmt = usrLocale.Country.DateTimeFormat;
            string discountAmountStr = String.Format(sysLocale.Currency.PriceFormat, numFmt.Convert(order.DiscountAmount));
            string taxAmountStr = String.Format(sysLocale.Currency.PriceFormat, numFmt.Convert(order.TaxAmount));
            string subtotalStr = String.Format(sysLocale.Currency.PriceFormat, numFmt.Convert(order.Subtotal));
            string totalStr = String.Format(sysLocale.Currency.PriceFormat, numFmt.Convert(order.Total));
            var customer = order.Customer;
            var seller = order.Seller;
            var billingAddress = order.BillingAddress;
            var shippingAddress = order.ShippingAddress;
            var sellerAddress = order.SellerAddress;

            var replacements = new Dictionary<String, String>
            {
                {"Recipient.EntityID", recipient.EntityID.ToString()},
                {"Recipient.Email", recipient.Email},
                {"Recipient.Username", recipient.Username},
                {"Recipient.Guid", recipient.Guid.ToString()},
                {"Order.Customer.EntityID", customer.EntityID.ToString()},
                {"Order.Customer.Account.EntityID", customer.Account.EntityID.ToString()},
                {"Order.Customer.Account.Guid", customer.Account.Guid.ToString()},
                {"Order.Customer.Account.Email", customer.Account.Email},
                {"Order.Customer.Account.Username", customer.Account.Username},
                {"Order.Seller.EntityID", seller.EntityID.ToString()},
                {"Order.Seller.Account.EntityID", seller.Account.EntityID.ToString()},
                {"Order.Seller.Account.Guid", seller.Account.Guid.ToString()},
                {"Order.Seller.Account.Email", seller.Account.Email},
                {"Order.Seller.Account.Username", seller.Account.Username},
                {"Order.Seller.DisplayName", seller.DisplayName},
                {"Order.SalesModelID", order.SalesModelID.ToString()},
                {"Order.ShippingMethodID", order.ShippingMethodID.ToString()},
                {"Order.BillingAddress.Country", billingAddress.Country.Name},
                {"Order.BillingAddress.Region", billingAddress.Region.Name},
                {"Order.BillingAddress.City", billingAddress.City},
                {"Order.BillingAddress.Address1", billingAddress.Address1},
                {"Order.BillingAddress.Address2", billingAddress.Address2},
                {"Order.BillingAddress.ContactName", billingAddress.ContactName},
                {"Order.BillingAddress.PostalCode", billingAddress.PostalCode},
                {"Order.BillingAddress.PhoneNumber", billingAddress.PhoneNumber},
                {"Order.BillingAddress.FaxNumber", billingAddress.FaxNumber},
                {"Order.BillingAddress.Email", billingAddress.Email},
                {"Order.ShippingAddress.Country", shippingAddress.Country.Name},
                {"Order.ShippingAddress.Region", shippingAddress.Region.Name},
                {"Order.ShippingAddress.City", shippingAddress.City},
                {"Order.ShippingAddress.Address1", shippingAddress.Address1},
                {"Order.ShippingAddress.Address2", shippingAddress.Address2},
                {"Order.ShippingAddress.ContactName",shippingAddress.ContactName},
                {"Order.ShippingAddress.PostalCode", shippingAddress.PostalCode},
                {"Order.ShippingAddress.PhoneNumber", shippingAddress.PhoneNumber},
                {"Order.ShippingAddress.FaxNumber", shippingAddress.FaxNumber},
                {"Order.ShippingAddress.Email", shippingAddress.Email},
                {"Order.SellerAddress.Country", sellerAddress.Country.Name},
                {"Order.SellerAddress.Region", sellerAddress.Region.Name},
                {"Order.SellerAddress.City", sellerAddress.City},
                {"Order.SellerAddress.Address1", sellerAddress.Address1},
                {"Order.SellerAddress.Address2", sellerAddress.Address2},
                {"Order.SellerAddress.ContactName", sellerAddress.ContactName},
                {"Order.SellerAddress.EntityName", sellerAddress.EntityName},
                {"Order.SellerAddress.PostalCode", sellerAddress.PostalCode},
                {"Order.SellerAddress.PhoneNumber", sellerAddress.PhoneNumber},
                {"Order.SellerAddress.FaxNumber", sellerAddress.FaxNumber},
                {"Order.SellerAddress.Email", sellerAddress.Email},
                {"Order.SellerAddress.Website", sellerAddress.Website},
                {"Order.EntityID", order.EntityID.ToString()},
                {"Order.Guid", order.Guid.ToString()},
                {"Order.Status", FlagsHelper.G1_Flags(order.Flags).ToString()},
                {"Order.DiscountAmount", discountAmountStr},
                {"Order.TaxAmount", taxAmountStr},
                {"Order.Subtotal", subtotalStr},
                {"Order.Total", totalStr},
                {"Order.CreatedOn", order.CreatedOn.ToLocal(utcOffset).ToStringByFmt(dtFmt)}
            };

            result = uow.Execute(new CreateTemplatedMessageByCode
            {
                TemplateCode = "NORDCFRM",
                LanguageID = usrLocale.Language.EntityID,
                Replacements = replacements,
                To = recipient.Email,
                Flags = MessageFlags.Active
            }).Result;
        }

        #endregion
    }

    /// <summary>
    /// Enqueues an order processing notification message.
    /// </summary>
    /// <remarks>
    /// Message template code: NORDPROC
    /// Allowed tokens: [T:Recipient.EntityID], [T:Recipient.Guid], [T:Recipient.Email], [T:Recipient.Username],
    /// [T:Order.Customer.EntityID], [T:Order.Customer.Account.Email], [T:Order.Customer.Account.Username],
    /// [T:Order.Customer.Account.Guid], [T:Order.Seller.EntityID], [T:Order.Seller.Account.Email],
    /// [T:Order.Seller.Account.Username], [T:Order.Seller.Account.Guid], [T:Order.ShippingMethodID],
    /// [T:Order.BillingAddress.Country], [T:Order.BillingAddress.Region], [T:Order.BillingAddress.City],
    /// [T:Order.BillingAddress.Address1], [T:Order.BillingAddress.Address2], [T:Order.BillingAddress.ContactName],
    /// [T:Order.BillingAddress.PostalCode], [T:Order.BillingAddress.PhoneNumber], [T:Order.BillingAddress.FaxNumber],
    /// [T:Order.BillingAddress.Email], [T:Order.ShippingAddress.Country], [T:Order.ShippingAddress.Region],
    /// [T:Order.ShippingAddress.City], [T:Order.ShippingAddress.Address1], [T:Order.ShippingAddress.Address2],
    /// [T:Order.ShippingAddress.ContactName], [T:Order.ShippingAddress.PostalCode], [T:Order.ShippingAddress.PhoneNumber],
    /// [T:Order.ShippingAddress.FaxNumber], [T:Order.ShippingAddress.Email], [T:Order.SellerAddress.Country],
    /// [T:Order.SellerAddress.Region], [T:Order.SellerAddress.City], [T:Order.SellerAddress.Address1],
    /// [T:Order.SellerAddress.Address2], [T:Order.SellerAddress.ContactName], [T:Order.SellerAddress.PostalCode],
    /// [T:Order.SellerAddress.PhoneNumber], [T:Order.SellerAddress.FaxNumber], [T:Order.SellerAddress.Email],
    /// [T:Order.Guid], [T:Order.DiscountAmount], [T:Order.TaxAmount], [T:Order.Subtotal], [T:Order.Total],
    /// [T:Order.CreatedOn], [T:Seller.DisplayName], [T:Order.Customer.Account.EntityID],
    /// [T:Order.Seller.Account.EntityID], [T:Order.SellerAddress.EntityName], [T:Order.Status], 
    /// [T:Order.SellerAddress.Website], [T:Order.EntityID]
    /// </remarks>
    public class EnqueueOrderProcessNofitication : Operation<MessageEntity>
    {
        #region Properties

        /// <summary>
        /// Gets or sets the order identifier.
        /// </summary>
        public int OrderID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the recipient identifier.
        /// </summary>
        public int RecipientID
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override void Work(IUnitOfWork uow, out MessageEntity result)
        {
            var order = uow.Execute(new GetOrder
            {
                ThrowsException = true,
                EntityID = OrderID
            }).Result;
            var recipient = uow.Execute(new GetAccount
            {
                ThrowsException = true,
                EntityID = RecipientID
            }).Result;
            var sysLocale = uow.Execute(new GetLocalizationProfileByCode
            {
                ThrowsException = true,
                Code = uow.Execute(new GetConfigParamValueByKey
                {
                    Key = Par.In("Core").Of<LocalizationProfileEntity>().Name("System")
                }).Result
            }).Result;
            var usrLocale = recipient.LocalizationProfile;
            var numFmt = usrLocale.Country.NumericFormat;
            int utcOffset = usrLocale.TimeZone.UtcOffset;
            string dtFmt = usrLocale.Country.DateTimeFormat;
            string discountAmountStr = String.Format(sysLocale.Currency.PriceFormat, numFmt.Convert(order.DiscountAmount));
            string taxAmountStr = String.Format(sysLocale.Currency.PriceFormat, numFmt.Convert(order.TaxAmount));
            string subtotalStr = String.Format(sysLocale.Currency.PriceFormat, numFmt.Convert(order.Subtotal));
            string totalStr = String.Format(sysLocale.Currency.PriceFormat, numFmt.Convert(order.Total));
            var customer = order.Customer;
            var seller = order.Seller;
            var billingAddress = order.BillingAddress;
            var shippingAddress = order.ShippingAddress;
            var sellerAddress = order.SellerAddress;

            var replacements = new Dictionary<String, String>
            {
                {"Recipient.EntityID", recipient.EntityID.ToString()},
                {"Recipient.Email", recipient.Email},
                {"Recipient.Username", recipient.Username},
                {"Recipient.Guid", recipient.Guid.ToString()},
                {"Order.Customer.EntityID", customer.EntityID.ToString()},
                {"Order.Customer.Account.EntityID", customer.Account.EntityID.ToString()},
                {"Order.Customer.Account.Guid", customer.Account.Guid.ToString()},
                {"Order.Customer.Account.Email", customer.Account.Email},
                {"Order.Customer.Account.Username", customer.Account.Username},
                {"Order.Seller.EntityID", seller.EntityID.ToString()},
                {"Order.Seller.Account.EntityID", seller.Account.EntityID.ToString()},
                {"Order.Seller.Account.Guid", seller.Account.Guid.ToString()},
                {"Order.Seller.Account.Email", seller.Account.Email},
                {"Order.Seller.Account.Username", seller.Account.Username},
                {"Order.Seller.DisplayName", seller.DisplayName},
                {"Order.SalesModelID", order.SalesModelID.ToString()},
                {"Order.ShippingMethodID", order.ShippingMethodID.ToString()},
                {"Order.BillingAddress.Country", billingAddress.Country.Name},
                {"Order.BillingAddress.Region", billingAddress.Region.Name},
                {"Order.BillingAddress.City", billingAddress.City},
                {"Order.BillingAddress.Address1", billingAddress.Address1},
                {"Order.BillingAddress.Address2", billingAddress.Address2},
                {"Order.BillingAddress.ContactName", billingAddress.ContactName},
                {"Order.BillingAddress.PostalCode", billingAddress.PostalCode},
                {"Order.BillingAddress.PhoneNumber", billingAddress.PhoneNumber},
                {"Order.BillingAddress.FaxNumber", billingAddress.FaxNumber},
                {"Order.BillingAddress.Email", billingAddress.Email},
                {"Order.ShippingAddress.Country", shippingAddress.Country.Name},
                {"Order.ShippingAddress.Region", shippingAddress.Region.Name},
                {"Order.ShippingAddress.City", shippingAddress.City},
                {"Order.ShippingAddress.Address1", shippingAddress.Address1},
                {"Order.ShippingAddress.Address2", shippingAddress.Address2},
                {"Order.ShippingAddress.ContactName",shippingAddress.ContactName},
                {"Order.ShippingAddress.PostalCode", shippingAddress.PostalCode},
                {"Order.ShippingAddress.PhoneNumber", shippingAddress.PhoneNumber},
                {"Order.ShippingAddress.FaxNumber", shippingAddress.FaxNumber},
                {"Order.ShippingAddress.Email", shippingAddress.Email},
                {"Order.SellerAddress.Country", sellerAddress.Country.Name},
                {"Order.SellerAddress.Region", sellerAddress.Region.Name},
                {"Order.SellerAddress.City", sellerAddress.City},
                {"Order.SellerAddress.Address1", sellerAddress.Address1},
                {"Order.SellerAddress.Address2", sellerAddress.Address2},
                {"Order.SellerAddress.ContactName", sellerAddress.ContactName},
                {"Order.SellerAddress.EntityName", sellerAddress.EntityName},
                {"Order.SellerAddress.PostalCode", sellerAddress.PostalCode},
                {"Order.SellerAddress.PhoneNumber", sellerAddress.PhoneNumber},
                {"Order.SellerAddress.FaxNumber", sellerAddress.FaxNumber},
                {"Order.SellerAddress.Email", sellerAddress.Email},
                {"Order.SellerAddress.Website", sellerAddress.Website},
                {"Order.EntityID", order.EntityID.ToString()},
                {"Order.Guid", order.Guid.ToString()},
                {"Order.Status", FlagsHelper.G1_Flags(order.Flags).ToString()},
                {"Order.DiscountAmount", discountAmountStr},
                {"Order.TaxAmount", taxAmountStr},
                {"Order.Subtotal", subtotalStr},
                {"Order.Total", totalStr},
                {"Order.CreatedOn", order.CreatedOn.ToLocal(utcOffset).ToStringByFmt(dtFmt)}
            };

            result = uow.Execute(new CreateTemplatedMessageByCode
            {
                TemplateCode = "NORDPROC",
                LanguageID = usrLocale.Language.EntityID,
                Replacements = replacements,
                To = recipient.Email,
                Flags = MessageFlags.Active
            }).Result;
        }

        #endregion
    }

    /// <summary>
    /// Enqueues an order cancel notification message.
    /// </summary>
    /// <remarks>
    /// Message template code: NORDCNCL
    /// Allowed tokens: [T:Recipient.EntityID], [T:Recipient.Guid], [T:Recipient.Email], [T:Recipient.Username],
    /// [T:Order.Customer.EntityID], [T:Order.Customer.Account.Email], [T:Order.Customer.Account.Username],
    /// [T:Order.Customer.Account.Guid], [T:Order.Seller.EntityID], [T:Order.Seller.Account.Email],
    /// [T:Order.Seller.Account.Username], [T:Order.Seller.Account.Guid], [T:Order.ShippingMethodID],
    /// [T:Order.BillingAddress.Country], [T:Order.BillingAddress.Region], [T:Order.BillingAddress.City],
    /// [T:Order.BillingAddress.Address1], [T:Order.BillingAddress.Address2], [T:Order.BillingAddress.ContactName],
    /// [T:Order.BillingAddress.PostalCode], [T:Order.BillingAddress.PhoneNumber], [T:Order.BillingAddress.FaxNumber],
    /// [T:Order.BillingAddress.Email], [T:Order.ShippingAddress.Country], [T:Order.ShippingAddress.Region],
    /// [T:Order.ShippingAddress.City], [T:Order.ShippingAddress.Address1], [T:Order.ShippingAddress.Address2],
    /// [T:Order.ShippingAddress.ContactName], [T:Order.ShippingAddress.PostalCode], [T:Order.ShippingAddress.PhoneNumber],
    /// [T:Order.ShippingAddress.FaxNumber], [T:Order.ShippingAddress.Email], [T:Order.SellerAddress.Country],
    /// [T:Order.SellerAddress.Region], [T:Order.SellerAddress.City], [T:Order.SellerAddress.Address1],
    /// [T:Order.SellerAddress.Address2], [T:Order.SellerAddress.ContactName], [T:Order.SellerAddress.PostalCode],
    /// [T:Order.SellerAddress.PhoneNumber], [T:Order.SellerAddress.FaxNumber], [T:Order.SellerAddress.Email],
    /// [T:Order.Guid], [T:Order.DiscountAmount], [T:Order.TaxAmount], [T:Order.Subtotal], [T:Order.Total],
    /// [T:Order.CreatedOn], [T:Seller.DisplayName], [T:Order.Customer.Account.EntityID],
    /// [T:Order.Seller.Account.EntityID], [T:Order.SellerAddress.EntityName], [T:Order.Status], 
    /// [T:Order.SellerAddress.Website], [T:Order.EntityID]
    /// </remarks>
    public class EnqueueOrderCancelNofitication : Operation<MessageEntity>
    {
        #region Properties

        /// <summary>
        /// Gets or sets the order identifier.
        /// </summary>
        public int OrderID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the recipient identifier.
        /// </summary>
        public int RecipientID
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override void Work(IUnitOfWork uow, out MessageEntity result)
        {
            var order = uow.Execute(new GetOrder
            {
                ThrowsException = true,
                EntityID = OrderID
            }).Result;
            var recipient = uow.Execute(new GetAccount
            {
                ThrowsException = true,
                EntityID = RecipientID
            }).Result;
            var sysLocale = uow.Execute(new GetLocalizationProfileByCode
            {
                ThrowsException = true,
                Code = uow.Execute(new GetConfigParamValueByKey
                {
                    Key = Par.In("Core").Of<LocalizationProfileEntity>().Name("System")
                }).Result
            }).Result;
            var usrLocale = recipient.LocalizationProfile;
            var numFmt = usrLocale.Country.NumericFormat;
            int utcOffset = usrLocale.TimeZone.UtcOffset;
            string dtFmt = usrLocale.Country.DateTimeFormat;
            string discountAmountStr = String.Format(sysLocale.Currency.PriceFormat, numFmt.Convert(order.DiscountAmount));
            string taxAmountStr = String.Format(sysLocale.Currency.PriceFormat, numFmt.Convert(order.TaxAmount));
            string subtotalStr = String.Format(sysLocale.Currency.PriceFormat, numFmt.Convert(order.Subtotal));
            string totalStr = String.Format(sysLocale.Currency.PriceFormat, numFmt.Convert(order.Total));
            var customer = order.Customer;
            var seller = order.Seller;
            var billingAddress = order.BillingAddress;
            var shippingAddress = order.ShippingAddress;
            var sellerAddress = order.SellerAddress;

            var replacements = new Dictionary<String, String>
            {
                {"Recipient.EntityID", recipient.EntityID.ToString()},
                {"Recipient.Email", recipient.Email},
                {"Recipient.Username", recipient.Username},
                {"Recipient.Guid", recipient.Guid.ToString()},
                {"Order.Customer.EntityID", customer.EntityID.ToString()},
                {"Order.Customer.Account.EntityID", customer.Account.EntityID.ToString()},
                {"Order.Customer.Account.Guid", customer.Account.Guid.ToString()},
                {"Order.Customer.Account.Email", customer.Account.Email},
                {"Order.Customer.Account.Username", customer.Account.Username},
                {"Order.Seller.EntityID", seller.EntityID.ToString()},
                {"Order.Seller.Account.EntityID", seller.Account.EntityID.ToString()},
                {"Order.Seller.Account.Guid", seller.Account.Guid.ToString()},
                {"Order.Seller.Account.Email", seller.Account.Email},
                {"Order.Seller.Account.Username", seller.Account.Username},
                {"Order.Seller.DisplayName", seller.DisplayName},
                {"Order.SalesModelID", order.SalesModelID.ToString()},
                {"Order.ShippingMethodID", order.ShippingMethodID.ToString()},
                {"Order.BillingAddress.Country", billingAddress.Country.Name},
                {"Order.BillingAddress.Region", billingAddress.Region.Name},
                {"Order.BillingAddress.City", billingAddress.City},
                {"Order.BillingAddress.Address1", billingAddress.Address1},
                {"Order.BillingAddress.Address2", billingAddress.Address2},
                {"Order.BillingAddress.ContactName", billingAddress.ContactName},
                {"Order.BillingAddress.PostalCode", billingAddress.PostalCode},
                {"Order.BillingAddress.PhoneNumber", billingAddress.PhoneNumber},
                {"Order.BillingAddress.FaxNumber", billingAddress.FaxNumber},
                {"Order.BillingAddress.Email", billingAddress.Email},
                {"Order.ShippingAddress.Country", shippingAddress.Country.Name},
                {"Order.ShippingAddress.Region", shippingAddress.Region.Name},
                {"Order.ShippingAddress.City", shippingAddress.City},
                {"Order.ShippingAddress.Address1", shippingAddress.Address1},
                {"Order.ShippingAddress.Address2", shippingAddress.Address2},
                {"Order.ShippingAddress.ContactName",shippingAddress.ContactName},
                {"Order.ShippingAddress.PostalCode", shippingAddress.PostalCode},
                {"Order.ShippingAddress.PhoneNumber", shippingAddress.PhoneNumber},
                {"Order.ShippingAddress.FaxNumber", shippingAddress.FaxNumber},
                {"Order.ShippingAddress.Email", shippingAddress.Email},
                {"Order.SellerAddress.Country", sellerAddress.Country.Name},
                {"Order.SellerAddress.Region", sellerAddress.Region.Name},
                {"Order.SellerAddress.City", sellerAddress.City},
                {"Order.SellerAddress.Address1", sellerAddress.Address1},
                {"Order.SellerAddress.Address2", sellerAddress.Address2},
                {"Order.SellerAddress.ContactName", sellerAddress.ContactName},
                {"Order.SellerAddress.EntityName", sellerAddress.EntityName},
                {"Order.SellerAddress.PostalCode", sellerAddress.PostalCode},
                {"Order.SellerAddress.PhoneNumber", sellerAddress.PhoneNumber},
                {"Order.SellerAddress.FaxNumber", sellerAddress.FaxNumber},
                {"Order.SellerAddress.Email", sellerAddress.Email},
                {"Order.SellerAddress.Website", sellerAddress.Website},
                {"Order.EntityID", order.EntityID.ToString()},
                {"Order.Guid", order.Guid.ToString()},
                {"Order.Status", FlagsHelper.G1_Flags(order.Flags).ToString()},
                {"Order.DiscountAmount", discountAmountStr},
                {"Order.TaxAmount", taxAmountStr},
                {"Order.Subtotal", subtotalStr},
                {"Order.Total", totalStr},
                {"Order.CreatedOn", order.CreatedOn.ToLocal(utcOffset).ToStringByFmt(dtFmt)}
            };

            result = uow.Execute(new CreateTemplatedMessageByCode
            {
                TemplateCode = "NORDCNCL",
                LanguageID = usrLocale.Language.EntityID,
                Replacements = replacements,
                To = recipient.Email,
                Flags = MessageFlags.Active
            }).Result;
        }

        #endregion
    }

    /// <summary>
    /// Enqueues an order complete notification message.
    /// </summary>
    /// <remarks>
    /// Message template code: NORDCMPL
    /// Allowed tokens: [T:Recipient.EntityID], [T:Recipient.Guid], [T:Recipient.Email], [T:Recipient.Username],
    /// [T:Order.Customer.EntityID], [T:Order.Customer.Account.Email], [T:Order.Customer.Account.Username],
    /// [T:Order.Customer.Account.Guid], [T:Order.Seller.EntityID], [T:Order.Seller.Account.Email],
    /// [T:Order.Seller.Account.Username], [T:Order.Seller.Account.Guid], [T:Order.ShippingMethodID],
    /// [T:Order.BillingAddress.Country], [T:Order.BillingAddress.Region], [T:Order.BillingAddress.City],
    /// [T:Order.BillingAddress.Address1], [T:Order.BillingAddress.Address2], [T:Order.BillingAddress.ContactName],
    /// [T:Order.BillingAddress.PostalCode], [T:Order.BillingAddress.PhoneNumber], [T:Order.BillingAddress.FaxNumber],
    /// [T:Order.BillingAddress.Email], [T:Order.ShippingAddress.Country], [T:Order.ShippingAddress.Region],
    /// [T:Order.ShippingAddress.City], [T:Order.ShippingAddress.Address1], [T:Order.ShippingAddress.Address2],
    /// [T:Order.ShippingAddress.ContactName], [T:Order.ShippingAddress.PostalCode], [T:Order.ShippingAddress.PhoneNumber],
    /// [T:Order.ShippingAddress.FaxNumber], [T:Order.ShippingAddress.Email], [T:Order.SellerAddress.Country],
    /// [T:Order.SellerAddress.Region], [T:Order.SellerAddress.City], [T:Order.SellerAddress.Address1],
    /// [T:Order.SellerAddress.Address2], [T:Order.SellerAddress.ContactName], [T:Order.SellerAddress.PostalCode],
    /// [T:Order.SellerAddress.PhoneNumber], [T:Order.SellerAddress.FaxNumber], [T:Order.SellerAddress.Email],
    /// [T:Order.Guid], [T:Order.DiscountAmount], [T:Order.TaxAmount], [T:Order.Subtotal], [T:Order.Total],
    /// [T:Order.CreatedOn], [T:Seller.DisplayName], [T:Order.Customer.Account.EntityID],
    /// [T:Order.Seller.Account.EntityID], [T:Order.SellerAddress.EntityName], [T:Order.Status], 
    /// [T:Order.SellerAddress.Website], [T:Order.EntityID]
    /// </remarks>
    public class EnqueueOrderCompleteNofitication : Operation<MessageEntity>
    {
        #region Properties

        /// <summary>
        /// Gets or sets the order identifier.
        /// </summary>
        public int OrderID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the recipient identifier.
        /// </summary>
        public int RecipientID
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override void Work(IUnitOfWork uow, out MessageEntity result)
        {
            var order = uow.Execute(new GetOrder
            {
                ThrowsException = true,
                EntityID = OrderID
            }).Result;
            var recipient = uow.Execute(new GetAccount
            {
                ThrowsException = true,
                EntityID = RecipientID
            }).Result;
            var sysLocale = uow.Execute(new GetLocalizationProfileByCode
            {
                ThrowsException = true,
                Code = uow.Execute(new GetConfigParamValueByKey
                {
                    Key = Par.In("Core").Of<LocalizationProfileEntity>().Name("System")
                }).Result
            }).Result;
            var usrLocale = recipient.LocalizationProfile;
            var numFmt = usrLocale.Country.NumericFormat;
            int utcOffset = usrLocale.TimeZone.UtcOffset;
            string dtFmt = usrLocale.Country.DateTimeFormat;
            string discountAmountStr = String.Format(sysLocale.Currency.PriceFormat, numFmt.Convert(order.DiscountAmount));
            string taxAmountStr = String.Format(sysLocale.Currency.PriceFormat, numFmt.Convert(order.TaxAmount));
            string subtotalStr = String.Format(sysLocale.Currency.PriceFormat, numFmt.Convert(order.Subtotal));
            string totalStr = String.Format(sysLocale.Currency.PriceFormat, numFmt.Convert(order.Total));
            var customer = order.Customer;
            var seller = order.Seller;
            var billingAddress = order.BillingAddress;
            var shippingAddress = order.ShippingAddress;
            var sellerAddress = order.SellerAddress;

            var replacements = new Dictionary<String, String>
            {
                {"Recipient.EntityID", recipient.EntityID.ToString()},
                {"Recipient.Email", recipient.Email},
                {"Recipient.Username", recipient.Username},
                {"Recipient.Guid", recipient.Guid.ToString()},
                {"Order.Customer.EntityID", customer.EntityID.ToString()},
                {"Order.Customer.Account.EntityID", customer.Account.EntityID.ToString()},
                {"Order.Customer.Account.Guid", customer.Account.Guid.ToString()},
                {"Order.Customer.Account.Email", customer.Account.Email},
                {"Order.Customer.Account.Username", customer.Account.Username},
                {"Order.Seller.EntityID", seller.EntityID.ToString()},
                {"Order.Seller.Account.EntityID", seller.Account.EntityID.ToString()},
                {"Order.Seller.Account.Guid", seller.Account.Guid.ToString()},
                {"Order.Seller.Account.Email", seller.Account.Email},
                {"Order.Seller.Account.Username", seller.Account.Username},
                {"Order.Seller.DisplayName", seller.DisplayName},
                {"Order.SalesModelID", order.SalesModelID.ToString()},
                {"Order.ShippingMethodID", order.ShippingMethodID.ToString()},
                {"Order.BillingAddress.Country", billingAddress.Country.Name},
                {"Order.BillingAddress.Region", billingAddress.Region.Name},
                {"Order.BillingAddress.City", billingAddress.City},
                {"Order.BillingAddress.Address1", billingAddress.Address1},
                {"Order.BillingAddress.Address2", billingAddress.Address2},
                {"Order.BillingAddress.ContactName", billingAddress.ContactName},
                {"Order.BillingAddress.PostalCode", billingAddress.PostalCode},
                {"Order.BillingAddress.PhoneNumber", billingAddress.PhoneNumber},
                {"Order.BillingAddress.FaxNumber", billingAddress.FaxNumber},
                {"Order.BillingAddress.Email", billingAddress.Email},
                {"Order.ShippingAddress.Country", shippingAddress.Country.Name},
                {"Order.ShippingAddress.Region", shippingAddress.Region.Name},
                {"Order.ShippingAddress.City", shippingAddress.City},
                {"Order.ShippingAddress.Address1", shippingAddress.Address1},
                {"Order.ShippingAddress.Address2", shippingAddress.Address2},
                {"Order.ShippingAddress.ContactName",shippingAddress.ContactName},
                {"Order.ShippingAddress.PostalCode", shippingAddress.PostalCode},
                {"Order.ShippingAddress.PhoneNumber", shippingAddress.PhoneNumber},
                {"Order.ShippingAddress.FaxNumber", shippingAddress.FaxNumber},
                {"Order.ShippingAddress.Email", shippingAddress.Email},
                {"Order.SellerAddress.Country", sellerAddress.Country.Name},
                {"Order.SellerAddress.Region", sellerAddress.Region.Name},
                {"Order.SellerAddress.City", sellerAddress.City},
                {"Order.SellerAddress.Address1", sellerAddress.Address1},
                {"Order.SellerAddress.Address2", sellerAddress.Address2},
                {"Order.SellerAddress.ContactName", sellerAddress.ContactName},
                {"Order.SellerAddress.EntityName", sellerAddress.EntityName},
                {"Order.SellerAddress.PostalCode", sellerAddress.PostalCode},
                {"Order.SellerAddress.PhoneNumber", sellerAddress.PhoneNumber},
                {"Order.SellerAddress.FaxNumber", sellerAddress.FaxNumber},
                {"Order.SellerAddress.Email", sellerAddress.Email},
                {"Order.SellerAddress.Website", sellerAddress.Website},
                {"Order.EntityID", order.EntityID.ToString()},
                {"Order.Guid", order.Guid.ToString()},
                {"Order.Status", FlagsHelper.G1_Flags(order.Flags).ToString()},
                {"Order.DiscountAmount", discountAmountStr},
                {"Order.TaxAmount", taxAmountStr},
                {"Order.Subtotal", subtotalStr},
                {"Order.Total", totalStr},
                {"Order.CreatedOn", order.CreatedOn.ToLocal(utcOffset).ToStringByFmt(dtFmt)}
            };

            result = uow.Execute(new CreateTemplatedMessageByCode
            {
                TemplateCode = "NORDCMPL",
                LanguageID = usrLocale.Language.EntityID,
                Replacements = replacements,
                To = recipient.Email,
                Flags = MessageFlags.Active
            }).Result;
        }

        #endregion
    }

    #endregion
}
